package com.reservation.aspectj;

import com.reservation.annotation.AutoFill;
import com.reservation.constant.AutoFillConstant;
import com.reservation.context.BaseContext;
import com.reservation.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 自定义切面，实现公共字段自动填充处理逻辑
 * 目的：在数据库操作时，自动填充创建时间、创建人、更新时间、更新人等字段
 */
@Aspect  // 声明这是一个切面类
@Component  // 将该类注册为Spring组件，使其能被Spring容器管理
@Slf4j  // Lombok注解，自动生成日志对象log
public class AutoFillAspect {

    /**
     * 定义切入点
     * <p>
     * execution(* com.sky.mapper.*.*(..)) - 表示执行匹配，匹配com.sky.mapper包下所有类的所有方法
     *   - 第一个 * - 表示返回类型，星号代表任意返回类型
     *   - com.sky.mapper - 表示包路径，指定了要匹配的包
     *   - 第二个 * - 表示类名匹配，星号代表 com.sky.mapper 包下的任意类
     *   - 第三个 * - 表示方法名匹配，星号代表任意方法名
     *   - (..) - 表示参数匹配，双点表示匹配任意数量、任意类型的参数
     * <p>
     * && @annotation(com.sky.annotation.AutoFill) - 并且方法上有@AutoFill注解
     * <p>
     * 综合：匹配com.sky.mapper包下所有类的所有方法，且方法必须带有@AutoFill注解
     */
    @Pointcut("execution(* com.reservation.mapper.*.*(..)) && @annotation(com.reservation.annotation.AutoFill)")
    public void autoFillPointCut(){}

    /**
     * 前置通知，在目标方法执行前自动填充公共字段
     *
     * @param joinPoint 连接点，可以通过它获取目标方法的信息和参数
     */
    @Before("autoFillPointCut()")  // 指定在哪个切入点执行，这里是在autoFillPointCut()定义的切入点执行
    public void autoFill(JoinPoint joinPoint){
        log.info("开始进行公共字段自动填充...");

        // 1. 获取目标方法上的@AutoFill注解信息
        // 获取方法签名对象，包含了目标方法的相关信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 从方法签名中获取@AutoFill注解对象
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
        // 获取注解中指定的操作类型（INSERT或UPDATE）
        OperationType operationType = autoFill.value();

        // 2. 获取目标方法的参数（实体对象）
        Object[] args = joinPoint.getArgs();
        // 如果没有参数，直接返回，不进行处理
        if(args == null || args.length == 0){
            return;
        }
        // 约定第一个参数为实体对象
        Object entity = args[0];

        // 3. 准备要填充的数据
        LocalDateTime now = LocalDateTime.now();  // 当前时间
        Long currentId = BaseContext.getCurrentId();  // 当前登录用户ID

        // 4. 根据操作类型，通过反射为实体对象的不同属性赋值
        if(operationType == OperationType.INSERT){
            // 插入操作需要设置4个字段：创建时间、创建人、更新时间、更新人
            try {
                // 获取实体类中的setter方法
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                // 调用setter方法为实体对象属性赋值
                setCreateTime.invoke(entity, now);
                setCreateUser.invoke(entity, currentId);
                setUpdateTime.invoke(entity, now);
                setUpdateUser.invoke(entity, currentId);
            } catch (Exception e) {
                // 打印异常堆栈
                e.printStackTrace();
            }
        }else if(operationType == OperationType.UPDATE){
            // 更新操作只需设置2个字段：更新时间、更新人
            try {
                // 获取实体类中的更新时间和更新人的setter方法
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                // 调用setter方法为实体对象属性赋值
                setUpdateTime.invoke(entity, now);
                setUpdateUser.invoke(entity, currentId);
            } catch (Exception e) {
                // 打印异常堆栈
                e.printStackTrace();
            }
        }
    }
}